The development of the EDVAC computer system of 1948 is often cited as the beginning of the computer era. Since that time, computer systems have evolved into extremely sophisticated devices, and computer systems may be found in many different settings. Computer systems typically include a combination of hardware, such as semiconductors and circuit boards, and software, also known as computer programs. Human programmers often write computer programs in a form of computer language that is relatively easy for a human to understand, but which is not efficient for the computer the execute. Another program, such as a compiler or interpreter, then transforms the program into a form that is more efficient for the computer to execute, but relatively difficult for a human to understand.
Recently, Java became a prominent computer language with a wide application spectrum, from embedded systems to enterprise servers. A Java Virtual Machine (JVM) is a software layer that interprets and executes Java bytecodes. One of the major issues in using the Java programming language, or any interpreted language, is performance.
Unfortunately, a standard Java Virtual Machine does not typically yield high-performing programs. In order to increase performance, a technique called just-in-time (JIT) compilation is sometimes used to execute Java code inside the Java Virtual Machine. Through just-in-time compilation, a Java bytecode method is dynamically translated into a native method (code native to the computer on which the program is executing) as the method executes, so as to remove the interpretation overhead of a typical Java Virtual Machine implementation. Since the just-in-time compilation itself is part of the total execution time of a Java program, in order to be useful the compilation must be fast and the benefit from compilation must outweigh the just-in-time compilation overhead. Consequently, the implementation of a Java Virtual Machine with a just-in-time compiler requires many design choices in order to optimize performance of the executing program.
One such design choice involves determining what code to execute inline and what code to execute out-of-line via a method invocation. While it might be tempting to execute all code inline in an attempt to provide maximum performance, such a strategy may lead to a very large amount of generated code, which is often called code bloat. Bloated code has several drawbacks, including consuming more memory in the instruction cache of the processor. Thus, too much inlined code can have a negative impact on performance.
One type of code that is susceptible to code bloat is a feature called runtime class-type checking. Many modern object oriented computer languages, including Java, offer runtime class-type checking, which helps maintain the integrity of programs by ensuring the program is manipulating the correct (expected) classes of objects. (Examples of class-type checking are the use of the “checkcast” and “instance of” Java bytecodes.) But, as is the case with most data integrity/coherency checks, there is a performance cost for doing runtime checking. Thus, anything that can be done to minimize the cost of runtime class-type checking will have a positive impact on the performance of applications.
One way to perform runtime class-type checking is to compare the class of a current object with an expected class type. If they are the same, the check succeeds and execution continues normally. If they are different, the object's entire class hierarchy must be traversed, comparing each of the object's superclasses with the expected class type. Although this solution is simple to implement and generic, it does not perform well if the expected class type is a superclass of an object's class, which is a fairly common occurrence in object-oriented applications. Furthermore, when generating code using a just-in-time compiler, a check implemented like this must be at least partially implemented out-of-line via an external function because traversing a class hierarchy requires more code than can be reasonably inlined at every class-type check site within the program. Unfortunately, any out-of-line function call negatively impacts a program's performance.
Another way to perform runtime class-type checking is to cache some number of the most-recently-encountered object class types and use a hash lookup in order to minimize the access time of the cache. The rationale for this caching is that if an object of a certain class type was just encountered, it is likely that the class type will be encountered again sometime soon. This caching mechanism works well in that it can keep track of both successful and failed class-type checks. One downside to caching is the overhead of updating the cache at runtime. This caching overhead is usually not trivial, so just like the previous solution, the cache checking and updating needs to be done in an out-of-line function, which again negatively impacts performance. Finally, the caching solution could potentially have some problems in a multi-threaded environment, where the cache is constantly being modified by multiple threads.
Without a better way to perform runtime class-type checking, computer users will continue to suffer from poor performance. Although the aforementioned problems have been described in the environment of Java, they can apply to any computer program that needs runtime class-type checking.